package com.happysmile.tsm;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.HashUtil;
import cn.hutool.system.SystemUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.happysmile.tsm.annotation.Timer;
import com.happysmile.tsm.config.NeedEnhance;
import com.happysmile.tsm.model.TaskDto;
import com.happysmile.tsm.mybatis.entity.Script;
import com.happysmile.tsm.mybatis.entity.Task;
import com.happysmile.tsm.mybatis.mapper.ScriptMapper;
import com.happysmile.tsm.mybatis.mapper.TaskMapper;
import com.happysmile.tsm.outter.R;
import com.happysmile.tsm.utils.ShellUtils;
import io.swagger.annotations.ApiOperation;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@RestController
@SpringBootApplication
@RequestMapping("/task")
@MapperScan("com.happysmile.tsm.mybatis.mapper")
public class TsmApplication  {

    /*
    * nohup java -jar springboot.jar > /dev/null 2>&1 &
    * */
    /*
    * 思路：通过拼接nohup这个系统命令，包装成一条守护进程
    *       通过kill -9 这个系统命令，来关闭一条进程
    * */
    /*
    * 无论是什么任务，都得先在数据库注册。
    * 定期扫描数据库中存在的任务，如果pid不存在，则置任务为结束。Timer
    * 无论什么任务结束，都要现在数据库中更新标记。
    * */
    private final String TASK_OPEN_PREFIX = "nohup ";
    private final String TASK_OPEN_SUFFIX = " > /dev/null 2>&1 &";
    private final String TASK_KILL_PREFIX = "kill -9 ";





    public static void main(String[] args) {
        //启动应用
        SpringApplication.run(TsmApplication.class, args);
    }


    @Timer(heartBeat = 500,durationTime = -1)
    @GetMapping("/hello")
    public void hello(){
        System.out.println("hello");
    }

    /*
    * private Timer
    * */

    @Resource
    private TaskMapper taskMapper;

    @Resource
    private ScriptMapper scriptMapper;

    /*
    * 定期清除任务timer,10s 运行1次。生命周期是无限。
    * */
    @NeedEnhance(why = "1.需要加入Timer的扫描类，自动注册",isAchieve = false,version = "1")
    @GetMapping("/program/check")
    @Transactional(rollbackFor = Exception.class)
    @Timer(heartBeat = 10000,durationTime = -1)
    void checkIsRunDoThings(){
        //查询所有未执行的任务
        List<Task> not_run_tasks = taskMapper.selectList(new QueryWrapper<Task>().eq("is_run", 0));
        for (Task not_run_task : not_run_tasks) {
            try {
                taskMapper.deleteById(not_run_task.getId());
            }catch (Exception e){}
        }
    }

    /*
    * 定期更新任务状态，通过执行shell的脚本查询指定任务，如果找不到，则更新数据库中的状态
    * */
    @NeedEnhance(why = "1.需要加入Timer的扫描类，自动注册",isAchieve = false,version = "1")
    @Transactional(rollbackFor = Exception.class)
    @GetMapping("/program/update")
    @Timer(heartBeat = 10000,durationTime = -1)
    void pu(){
        ShellUtils.run("chmod +x checkpid.sh");
        //查询所有正在运行的脚本的pid
        List<Task> run_tasks = taskMapper.selectList(new QueryWrapper<Task>().eq("is_run", 1));
        for (Task run_task : run_tasks) {
            Integer pid = run_task.getPid();
            //将pid以位置参数的方式传递给脚本，交由脚本输出结果。
            //1代表进程存在，0代表不存在
            String run = ShellUtils.run("./checkpid.sh " + pid);
            if (run.equals("1")){
                //进程存在
            }else {
                //进程不存在
                //更新数据库中的状态
                try {
                    taskMapper.update(null,new UpdateWrapper<Task>().set("is_run",0).eq("id",run_task.getId()));
                }catch (Exception e){
                    System.out.println("更新任务状态出错");
                }
            }
        }
    }


    @ApiOperation("查看运行的进程")
    @GetMapping("/ps/list")
    public R ps(){
        List<TaskDto> taskDtos = new ArrayList<TaskDto>();
        //从数据库中取出正在运行的进程
        String run = "";
        List<Task> run_tasks = taskMapper.selectList(new QueryWrapper<Task>().eq("is_run", 1));
        for (Task run_task : run_tasks) {
            Integer pid = run_task.getPid();
            //执行shell脚本，拼接
            //根据进程号得到对应的任务
            String shellCMD = "ps -p " + pid + " -o cmd";
            run = ShellUtils.run(shellCMD);
            System.out.println(run);
            TaskDto build = TaskDto.builder().path(run).pid(pid).build();
            taskDtos.add(build);
        }
        return R.success(taskDtos);
    }

    @PostMapping("/add")
    public R startTask(@RequestBody Task task){
        //0.将脚本转化为临时文件进行存储
        Script script = scriptMapper.selectById(task.getSid());
        //文件名的规则是temp_文件主键的hashcode
        //例如:temp_123232334_.sh
        int hashCode = HashUtil.jsHash(script.getContent());
        String tempFileName = "temp_" + hashCode + "_.sh";
        File file = new File(  "script/" +  tempFileName);
        String filePath = file.getAbsolutePath();
        try {
            boolean newFile = file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //将脚本写入临时文件
        try{
            FileUtil.writeUtf8String(script.getContent(),file);
        }catch (Exception e){}
        //为文件赋权限
        ShellUtils.run("chmod +x " + filePath);
        //1.启动这个任务，得到pid
        String pidFile = "command_" + hashCode + ".pid";
        ShellUtils.run(TASK_OPEN_PREFIX + filePath + TASK_OPEN_SUFFIX + "&echo $! > " + pidFile);//托管任务
        //1.1 打印上一条命令的执行结果，将它的pid放到这个文件
        ShellUtils.run("echo $! > command_ " + hashCode + ".pid");
        //2.加入数据库
        Integer mypid = null;
        try {
            mypid = Integer.valueOf(FileUtil.readUtf8String(pidFile));
        }catch (Exception e){}
        //将pid加入到任务体中
        task.setPid(mypid);
        //将任务插入到数据库中
        int insert = taskMapper.insert(task);
        if (insert != 0){
            return R.success("任务导入成功");
        }else {
            return R.fail("任务导入失败");
        }
    }

    @ApiOperation("关闭一条任务")
    @GetMapping("/delete/{pid}")
    public R kill(@PathVariable String pid){
        //1.shell中关闭
        ShellUtils.run(TASK_KILL_PREFIX + pid);
        //2.数据库中删除这条任务
        Task thisTask = taskMapper.selectOne(new QueryWrapper<Task>().eq("pid", Integer.valueOf(pid)));
        int i = taskMapper.deleteById(thisTask.getId());
        if (i == 1) return R.success("关闭成功");
        else return R.fail("关闭失败");
    }



}